package cn.yx.common.security.util;

import cn.hutool.core.collection.CollectionUtil;
import cn.yx.common.core.util.IdUtil;
import cn.yx.common.core.util.date.DateUtil;
import cn.yx.common.core.util.json.jackson.JsonUtil;
import cn.yx.common.security.pojo.JwtClaims;
import cn.yx.common.security.pojo.JwtUserDetails;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.CompressionCodecs;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.authority.SimpleGrantedAuthority;

import javax.crypto.SecretKey;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.time.temporal.TemporalUnit;
import java.util.*;

/**
 * <p>JWT工具类，生成Token，解析Token，判断Token是否过期</p>
 *
 * @author Wgssmart
 */
@Slf4j
public final class JwtUtil {

    public static final String JWT_CLAIMS = "jwt_claims";

    private JwtUtil() {
    }

    /**
     * 获取SecretKey
     *
     * @return
     */
    public static SecretKey generalKey() {
        return Keys.hmacShaKeyFor(Decoders.BASE64.decode("yxcloud+IsAVeryGoodFrameworkByWgssmart+2022="));
    }

    /**
     * 生成jwt token
     *
     * @param jwtUserDetails  JwtUserDetails对象
     * @param tokenExpiration token失效时间，单位为天
     * @return
     */
    public static String generateToken(JwtUserDetails jwtUserDetails, int tokenExpiration) {
        return Jwts.builder()
                .setId(IdUtil.simpleUuid())
                .setSubject(jwtUserDetails.getUsername())
                .setClaims(generateClaims(jwtUserDetails))
                .setExpiration(getExpirationDate(tokenExpiration, ChronoUnit.DAYS))
                .signWith(generalKey())
                .compressWith(CompressionCodecs.DEFLATE)
                .compact();
    }

    /**
     * 通过JwtUserDetails对象生成Claims map对象
     *
     * @param jwtUserDetails JwtUserDetails对象
     * @return
     */
    private static Map<String, Object> generateClaims(JwtUserDetails jwtUserDetails) {
        Map<String, Object> claims = new HashMap<>(1);

        JwtClaims jwtClaims = new JwtClaims(jwtUserDetails.getId(), jwtUserDetails.getUsername(),
                jwtUserDetails.getAccount(), jwtUserDetails.getPhone(), jwtUserDetails.getEmail(),
                jwtUserDetails.getNickname(), jwtUserDetails.getAvatar(), authorities(jwtUserDetails.getAuthorities()),
                jwtUserDetails.getTokenCreateTime());
        claims.put(JWT_CLAIMS, JsonUtil.toJsonStr(jwtClaims));
        return claims;
    }

    /**
     * 计算过期时间
     *
     * @param expiration
     * @return
     * @parma expirationUnit
     */
    private static Date getExpirationDate(int expiration, TemporalUnit expirationUnit) {
        LocalDateTime expireDateTime = DateUtil.localDateTime().plus(expiration, expirationUnit);
        return DateUtil.date(expireDateTime);
    }

    /**
     * 通过jwt token直接解析出JwtUserDetails对象
     *
     * @param token
     * @return
     */
    public static JwtUserDetails parseJwtUserDetails(String token) {
        Claims claims = parseToken(token);
        if (Objects.isNull(claims)) {
            return null;
        }
        JwtClaims jwtClaims = JsonUtil.toBean(claims.get(JWT_CLAIMS).toString(), JwtClaims.class);
        return convert(jwtClaims);
    }

    /**
     * 通过jwt token获取Claims对象
     *
     * @param token jwt token
     * @return
     */
    private static Claims parseToken(String token) {
        try {
            return Jwts.parserBuilder()
                    .setSigningKey(generalKey())
                    .build()
                    .parseClaimsJws(token)
                    .getBody();
        } catch (Exception e) {
            log.error("jwt token error!", e);
            return null;
        }
    }

    /**
     * 从JwtClaims转换到JwtUserDetails对象
     *
     * @param jwtClaims JwtClaims对象
     * @return
     */
    private static JwtUserDetails convert(JwtClaims jwtClaims) {
        JwtUserDetails jwtUserDetails = new JwtUserDetails();
        jwtUserDetails.setId(jwtClaims.getId());
        jwtUserDetails.setAccount(jwtClaims.getAccount());
        jwtUserDetails.setPhone(jwtClaims.getPhone());
        jwtUserDetails.setEmail(jwtClaims.getEmail());
        jwtUserDetails.setNickname(jwtClaims.getNickname());
        jwtUserDetails.setAvatar(jwtClaims.getAvatar());
        jwtUserDetails.setAuthorities(authorities(jwtClaims.getAuthorities()));
        jwtUserDetails.setEnabled(jwtClaims.getEnabled());
        jwtUserDetails.setAccountNonExpired(jwtClaims.getNonExpired());
        jwtUserDetails.setAccountNonLocked(jwtClaims.getNonLocked());
        jwtUserDetails.setCredentialsNonExpired(true);
        jwtUserDetails.setTokenCreateTime(jwtClaims.getCreateTime());
        return jwtUserDetails;
    }

    /**
     * 判断token是否过期
     *
     * @param token
     * @return
     */
    public static boolean isTokenExpired(String token) {
        JwtClaims jwtClaims = parseJwtClaims(token);
        Date expiration = getExpirationDate(jwtClaims.getTokenExpiration(), ChronoUnit.HOURS);
        return expiration.before(DateUtil.date());
    }

    /**
     * 通过jwt token获取Claims对象中存储的JwtClaims
     *
     * @param token jwt token
     * @return
     */
    private static JwtClaims parseJwtClaims(String token) {
        Claims claims = parseToken(token);
        return JsonUtil.toBean(claims.get(JWT_CLAIMS).toString(), JwtClaims.class);
    }

    /**
     * 从SimpleGrantedAuthority列表获取角色字符串列表
     *
     * @param simpleGrantedAuthorities
     * @return
     */
    private static List<String> authorities(Collection<SimpleGrantedAuthority> simpleGrantedAuthorities) {
        if (CollectionUtil.isEmpty(simpleGrantedAuthorities)) {
            return null;
        }
        List<String> authorities = new ArrayList<>(simpleGrantedAuthorities.size());
        for (SimpleGrantedAuthority authority : simpleGrantedAuthorities) {
            authorities.add(authority.getAuthority());
        }
        return authorities;
    }

    /**
     * 从角色字符串列表获取SimpleGrantedAuthority列表
     *
     * @param authorities
     * @return
     */
    private static List<SimpleGrantedAuthority> authorities(List<String> authorities) {
        if (CollectionUtil.isEmpty(authorities)) {
            return null;
        }
        List<SimpleGrantedAuthority> simpleGrantedAuthorities = new ArrayList<>(authorities.size());
        for (String authority : authorities) {
            simpleGrantedAuthorities.add(new SimpleGrantedAuthority(authority));
        }
        return simpleGrantedAuthorities;
    }

}